using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Fine.CRM.Common
{
    class ExtensionSettingCache
    {
        const int DEFAULT_CACHE_TIMEOUT = 1800; //30 minutes
        const int MAX_CACHE_TIMEOUT = 43200; //12 hours
        const int MIN_CACHE_TIMEOUT = 5; //5 seconds

        const string CACHE_SETTING_NAME = "CacheTimeOut";

        private static volatile ExtensionSettingCache instance;
        private static object syncRoot = new Object();

        private volatile Dictionary<string, string> _values = new Dictionary<string, string>();
        private DateTime  _refreshDateTime = DateTime.MinValue;
        private DateTime _expirationDateTime = DateTime.MinValue;

        public DateTime InstantiationDateTime { get; private set; }
        
        
        private ExtensionSettingCache() 
        {
            this.InstantiationDateTime = DateTime.UtcNow;
        }

        public static ExtensionSettingCache Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                            instance = new ExtensionSettingCache();
                    }
                }

                return instance;
            }
        }


        public T Get<T>(IOrganizationService OrgService, string Key, T DefaultValue)
        {
            this.RefreshIfExpired(OrgService);
            string value = this[Key];

            //if the item is not in the settings the return the passsed in default value;
            if (String.IsNullOrEmpty(value))
            {
                return DefaultValue;
            }

            //special handling for string[]
            if (typeof(T) == typeof(string[]))
            {
                //create a string array to return
                string[] strArray = new string[0];

                if (!string.IsNullOrEmpty(value))
                {
                    //normalize comma seperators to semicolons.
                    value = value.Replace(",", ";");

                    //remove trailing semicolon if it exists.
                    if (value[value.Length - 1] == ';')
                    {
                        value = value.Remove(value.Length - 1, 1);
                    }

                    //remove any white space following the seperator.
                    value = System.Text.RegularExpressions.Regex.Replace(value, @";\s+", ";");

                    //split the exlusion field string into an array
                    strArray = value.Split(';');
                }

                return (T)((object)strArray);
            }

            //Boolean value is false unless specifically set to 'true'
            if (typeof(T) == typeof(bool))
            {
                if (value.ToLower() == "true")
                {
                    return (T)((object)true);
                }
                
                return (T)((object)false);;
            }

            //integer values
            if (typeof(T) == typeof(int))
            {
                return (T)((object)Convert.ChangeType(int.Parse(value), typeof(int)));
            }

            //decimal values
            if (typeof(T) == typeof(decimal))
            {
                return (T)((object)Convert.ChangeType(decimal.Parse(value), typeof(decimal)));
            }

            //date time values
            if (typeof(T) == typeof(DateTime))
            {
                return (T)((object)Convert.ChangeType(DateTime.Parse(value), typeof(DateTime)));
            }

            //CRM Access Rights
            if (typeof(T) == typeof(Microsoft.Crm.Sdk.Messages.AccessRights))
            {
                return (T)((object)int.Parse(value));
            }

            //default handler
            return (T)((object)Convert.ChangeType(value, typeof(T)));

        }

        private string this[string key]
        {
            get
            {
                string searchKey = key.ToLower();
                string result = string.Empty;
                
                if (this._values != null && this._values.Keys.Contains(searchKey))
                {
                    result = this._values[searchKey];
                }
                
                return result;
            }
            set
            {
            }
        }

        private bool IsExpired()
        {
            if (DateTime.UtcNow > _expirationDateTime)
            {
                return true;
            }

            return false;
        }

        private void RefreshIfExpired(IOrganizationService OrgService)
        {
            //check for cache expiration
            if (this.IsExpired())
            {
                //lock access while refresh is being completed.
                lock (syncRoot)
                {

                    //test for expiration again because the cache may have been refreshed while this thread was waiting
                    if (this.IsExpired())
                    {
                        //clear the cached values
                        this._values.Clear();

                        //get values from the CRM Extenstion Settings entity.
                        try
                        {
                            //query the system for all active extension settings
                            QueryByAttribute qry = new QueryByAttribute("fine_extensionsetting");
                            qry.ColumnSet = new ColumnSet(new string[] { "fine_name", "fine_value", "fine_encrypted" });
                            qry.AddAttributeValue("statecode", 0);

                            EntityCollection result = OrgService.RetrieveMultiple(qry);

                            foreach (Entity setting in result.Entities)
                            {
                                string name = setting.GetValue<string>("fine_name", string.Empty).ToLower();
                                string value = setting.GetValue<string>("fine_value", string.Empty);
                                bool encrypted = setting.GetValue<bool>("fine_encrypted", false);

                                if (encrypted)
                                {
                                    value = Decrypt(value);
                                }

                                if (!this._values.Keys.Contains(name))
                                {
                                    this._values.Add(name, value);
                                }
                            }
                        }
                        catch (Exception ex) { }

                        //update the cache timeout
                        int timeoutOffset = 0;
                        if(!int.TryParse(this[CACHE_SETTING_NAME],out timeoutOffset))
                        { 
                            timeoutOffset = DEFAULT_CACHE_TIMEOUT;
                        }

                        if (timeoutOffset < MIN_CACHE_TIMEOUT)
                        {
                            timeoutOffset = MIN_CACHE_TIMEOUT;
                        }
                        else if (timeoutOffset > MAX_CACHE_TIMEOUT)
                        {
                            timeoutOffset = MAX_CACHE_TIMEOUT;
                        }
                        
                        this._refreshDateTime = DateTime.UtcNow;
                        this._expirationDateTime = this._refreshDateTime.AddSeconds(timeoutOffset);

                        
                    } //end if(IsExpired())
                } //end lock
            } //end if(IsExpired())
        }

        private static string Decrypt(string textToEncrypt)
        {
            string result;
            try
            {
                result = EncDec.Decrypt(textToEncrypt);
            }
            catch
            {
                result = string.Empty;
            }
            return result;
        }

    }

    public sealed class ExtensionSettings
    {
        private IOrganizationService _orgService = null;
        private string _childClassName = null;
        private ExtensionSettingCache _cache = ExtensionSettingCache.Instance;

        public ExtensionSettings(IOrganizationService OrgService, string ChildClassName)
        {
            this._orgService = OrgService;
            this._childClassName = ChildClassName;
        }

        public T Get<T>(string Key, T DefaultValue)
        {
            if (Key.Length > 0 && Key.Substring(0, 1) == ".")
            {
                Key = _childClassName + Key;
            }
            return _cache.Get<T>(_orgService, Key, DefaultValue);
        }

      
    }
}
